﻿namespace Profit
{
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System;

    /// <summary>
    /// Thread safe generic fixed size observable collection
    /// </summary>
    /// <typeparam name="T">The type of item stored in the collection</typeparam>
    public abstract class FixedSizeObservableCollection<T> : ObservableCollection<T>
    {
        private int? _maxItems;
        private readonly object _syncroot = new object();

        /// <summary>
        /// Occurs when an item is truncated because collection's count is greater than MaxItems value.
        /// </summary>
        public virtual event EventHandler<ItemTruncatedEventArgs<T>> ItemTruncated;

        /// <summary>
        /// Initializes a new instance of the <see cref="T:Profit.FixedSizeObservableCollection`1"/> class.
        /// </summary>
        protected FixedSizeObservableCollection(){}
        /// <summary>
        /// Initializes a new instance of the <see cref="T:Profit.FixedSizeObservableCollection`1"/> class.
        /// </summary>
        /// <param name="maxItemCount">The maximum items count</param>
        /// <exception cref="ArgumentOutOfRangeException">maxItemCount must be greater than 1.</exception>
        protected FixedSizeObservableCollection(int maxItemCount)
            : this()
        {
            if(maxItemCount < 1)
                throw new ArgumentOutOfRangeException();

            _maxItems = maxItemCount;
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="T:Profit.FixedSizeObservableCollection`1"/> class that contains elements copied from the specified list.
        /// </summary>
        /// <param name="list">The list from which the elements are copied.</param><exception cref="T:System.ArgumentNullException">The <paramref name="list"/> parameter cannot be null.</exception>
        protected FixedSizeObservableCollection(List<T> list) 
            : base(list)
        {
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="T:Profit.FixedSizeObservableCollection`1"/> class that contains elements copied from the specified collection.
        /// </summary>
        /// <param name="collection">The collection from which the elements are copied.</param><exception cref="T:System.ArgumentNullException">The <paramref name="collection"/> parameter cannot be null.</exception>
        protected FixedSizeObservableCollection(IEnumerable<T> collection) 
            : base(collection)
        {
        }
        /// <summary>
        /// Obtains or defines the maximum item count
        /// <remarks>Returns -1 if no maximum has been set</remarks>
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">maxItemCount must be greater than 1.</exception>
        public int MaxItems
        {
            get { return _maxItems.HasValue ? _maxItems.Value : -1; }
            set
            {
                if (value < 1)
                    throw new ArgumentOutOfRangeException();

                _maxItems = value;
            }
        }
        /// <summary>
        /// Adds an item to the collection and automatically trimming items if MaxItems is defined.
        /// </summary>
        /// <param name="item">The item to add</param>
        public virtual new void Add(T item)
        {
            lock(_syncroot)
            {
                if (_maxItems.HasValue)
                    while (Count > 0 && Count + 1 > _maxItems)
                    {
                        var truncated = this[0];
                        base.RemoveItem(0);
                        InvokeItemTruncated(new ItemTruncatedEventArgs<T>(truncated));
                    }

                base.Add(item);
            }
        }
        /// <summary>
        /// If MaxItems is defined, removes items until Count is less or equal to MaxItems.
        /// </summary>
        public virtual void TrimExcess()
        {
            lock (_syncroot)
                if(_maxItems.HasValue)
                    while (Count > _maxItems)
                    {
                        var truncated = this[0];
                        base.RemoveItem(0);
                        InvokeItemTruncated(new ItemTruncatedEventArgs<T>(truncated));
                    }
        }
        
        /// <summary>
        /// Removes all items from the collection.
        /// </summary>
        protected override void ClearItems()
        {
            lock(_syncroot)
                base.ClearItems();
        }
        /// <summary>
        /// Removes all items from the collection.
        /// </summary>
        public new void Clear()
        {
            ClearItems();
        }
        /// <summary>
        /// Raises the ItemTruncated event.
        /// </summary>
        protected internal void InvokeItemTruncated(ItemTruncatedEventArgs<T> e)
        {
            var handler = ItemTruncated;
            if (handler != null) handler(this, e);
        }

        #region NotSupported

        /// <summary>
        /// Not Supported. Always throws NotSupportedException.
        /// </summary>
        public new void RemoveAt(int index)
        {
            throw new NotSupportedException();
        }
        /// <summary>
        /// Not Supported. Always throws NotSupportedException.
        /// </summary>
        public new void Insert(int index, T item)
        {
            throw new NotSupportedException();
        }
        /// <summary>
        /// Not Supported. Always throws NotSupportedException.
        /// </summary>
        protected override void SetItem(int index, T item)
        {
            throw new NotSupportedException();
        }
        /// <summary>
        /// Not Supported. Always throws NotSupportedException.
        /// </summary>
        protected override void MoveItem(int oldIndex, int newIndex)
        {
            throw new NotSupportedException();
        }
        /// <summary>
        /// Not Supported. Always throws NotSupportedException.
        /// </summary>
        public new void Move(int oldIndex, int newIndex)
        {
            throw new NotSupportedException();
        }
        /// <summary>
        /// Not Supported. Always throws NotSupportedException.
        /// </summary>
        protected override void RemoveItem(int index)
        {
            throw new NotSupportedException();
        }

        #endregion
    }
}